use std::vec;

#[derive(Copy, Clone, Debug)]
pub struct DocInfo {
    pub id: u64,
    pub start_offset: u32,
    pub length: u32,
}

impl DocInfo {
    pub fn default() -> DocInfo {
        DocInfo {
            id: 0,
            start_offset: 0,
            length: 0,
        }
    }

    pub fn new(id: u64, start_offset: u32, length: u32) -> DocInfo {
        DocInfo {
            id,
            start_offset,
            length,
        }
    }
}

pub fn as_u32_be(array: &[u8]) -> u32 {
    ((array[0] as u32) << 24)
        + ((array[1] as u32) << 16)
        + ((array[2] as u32) << 8)
        + ((array[3] as u32) << 0)
}

pub fn as_u64_be(array: &[u8]) -> u64 {
    ((array[0] as u64) << 56)
        + ((array[1] as u64) << 48)
        + ((array[2] as u64) << 40)
        + ((array[3] as u64) << 32)
        + ((array[4] as u64) << 24)
        + ((array[5] as u64) << 16)
        + ((array[6] as u64) << 8)
        + ((array[7] as u64) << 0)
}

pub fn as_u32_le(array: &[u8]) -> u32 {
    ((array[0] as u32) << 0)
        + ((array[1] as u32) << 8)
        + ((array[2] as u32) << 16)
        + ((array[3] as u32) << 24)
}

pub fn to_bytes(input: &[u32]) -> Vec<u8> {
    let mut bytes = Vec::with_capacity(4 * input.len());

    for value in input {
        bytes.extend(&value.to_be_bytes());
    }

    bytes
}

pub fn from_bytes(input: &[u8]) -> Vec<u32> {
    let mut output = Vec::with_capacity(input.len() / 4);

    for i in 0..input.len() / 4 {
        let bytes = &input[i * 4..i * 4 + 4];
        output.push(as_u32_be(bytes));
    }

    output
}

pub fn struct_to_bytes(input: &[DocInfo]) -> Vec<u8> {
    let mut bytes = Vec::with_capacity(16 * input.len());

    for value in input {
        bytes.extend(&value.id.to_be_bytes());
        bytes.extend(&value.start_offset.to_be_bytes());
        bytes.extend(&value.length.to_be_bytes());
    }

    bytes
}

pub fn struct_from_bytes(input: &[u8]) -> Vec<DocInfo> {
    let mut output = Vec::with_capacity(input.len() / 16);

    for i in 0..input.len() / 16 {
        let bytes0 = &input[i * 16..i * 16 + 8];
        let bytes1 = &input[i * 16 + 8..i * 16 + 12];
        let bytes2 = &input[i * 16 + 12..i * 16 + 16];

        let doc = DocInfo {
            id: as_u64_be(bytes0),
            start_offset: as_u32_be(bytes1),
            length: as_u32_be(bytes2),
        };
        output.push(doc);
    }

    output
}

#[cfg(test)]
mod tests {
    #[test]
    fn to_bytes() {
        use super::*;

        assert_eq!(to_bytes(&[]), &[] as &[u8]);

        let input = &[0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
        let output = &[
            0x67, 0x45, 0x23, 0x01, 0xEF, 0xCD, 0xAB, 0x89, 0x98, 0xBA, 0xDC, 0xFE, 0x10, 0x32,
            0x54, 0x76, 0xC3, 0xD2, 0xE1, 0xF0,
        ];

        assert_eq!(to_bytes(input), output);

        let converted_back = from_bytes(&to_bytes(input)[..]);
        assert_eq!(converted_back, input);

        let doc = DocInfo::new(987654321, 2, 3);
        let docs = [doc, doc];
        let bs = struct_to_bytes(&docs);
        let converted_back = struct_from_bytes(&bs);
        println!("docs: {:?}", docs);
        println!(
            "bs.len: {}, converted_back.len: {}",
            bs.len(),
            converted_back.len()
        );
        println!("\nconverted back[1].id {}", converted_back[1].id);
        println!("converted back docs: {:?}", converted_back);
        assert_eq!(doc.id, converted_back[1].id);
    }
}
